package com.seteam.collabcrossword;

import com.seteam.collabcrossword.exceptions.CollabCrosswordException;
import com.seteam.collabcrossword.exceptions.CoordinatesException;
import com.seteam.collabcrossword.types.LetterType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * This class takes in CSV files that contain clues, and their solutions, and
 * generates instances of {@link Crossword}, {@link CrosswordCurrentState},
 * and {@link CrosswordSolution}. It also populates lists which will contain
 * clues of words going across and down.
 */
public class CrosswordGenerator {
    private final Logger logger = LoggerFactory
            .getLogger(CrosswordGenerator.class);
    private Crossword crossword;
    private CrosswordSolution solution;
    private CrosswordCurrentState currentState;
    private List<Clue> across;
    private List<Clue> down;
    private int[][] layout;
    private int dimX;
    private int dimY;
    private static final String ACROSS = "Across";
    private static final String DOWN = "Down";
    private static final char UNKNOWN = '-';
    private char[][] charMap;

    /**
     * Integer that represents the start of a word going both across
     * and down
     */
    private static final int START_OF_WORD = 3;

    /**
     * Integer that represents the start of a word going across only
     */
    private static final int START_ACROSS = 6;

    /**
     * Integer that represents the start of a word going down only
     */
    private static final int START_DOWN = 7;

    /**
     * Integer that represents a shared letter
     */
    private static final int SHARED = 5;

    /**
     * Integer that represents a blocked tile
     */
    private static final int BLOCKED = 0;

    /**
     * Integer that represents a default tile
     */
    private static final int DEFAULT = 1;

    // Clue number two dimensional array
    private int[][] clueNumberArray;

    /**
     * Constructor
     *
     * @param dimX the width of the board
     * @param dimY the height of the board
     * @throws CollabCrosswordException if there is any error in initializing the fields
     */
    public CrosswordGenerator(final int dimX, final int dimY)
            throws CollabCrosswordException {
        this.dimX = dimX;
        this.dimY = dimY;
        charMap = new char[dimX][dimY];
        clueNumberArray = new int[dimX][dimY];

        crossword = new Crossword(dimX, dimY);
        currentState = new CrosswordCurrentState(dimX, dimY);
        solution = new CrosswordSolution(dimX, dimY);
        across = new ArrayList<Clue>();
        down = new ArrayList<Clue>();
    }

    /**
     * Initializer for the board layout
     *
     * @return an array that represents that board's layout
     */
    private int[][] initBoardLayout() {
        final int tempLayout[][] = {
                {3, 7, 7, 0, 3, 7, 7, 0, 3, 7, 7, 7, 7},
                {6, 1, 1, 0, 6, 1, 1, 0, 6, 1, 1, 1, 1},
                {6, 1, 1, 7, 1, 1, 1, 0, 6, 1, 1, 1, 1},
                {6, 1, 1, 1, 1, 1, 1, 7, 1, 1, 0, 0, 0},
                {6, 1, 1, 1, 0, 6, 1, 1, 1, 0, 3, 7, 7},
                {0, 0, 0, 3, 7, 1, 0, 6, 1, 7, 1, 1, 1},
                {3, 7, 7, 1, 1, 0, 0, 0, 6, 1, 1, 1, 1},
                {6, 1, 1, 1, 1, 7, 0, 3, 1, 1, 0, 0, 0},
                {6, 1, 1, 0, 3, 1, 7, 1, 0, 3, 7, 7, 7},
                {0, 0, 0, 3, 1, 1, 1, 1, 7, 1, 1, 1, 1},
                {3, 7, 7, 1, 1, 0, 6, 1, 1, 1, 1, 1, 1},
                {6, 1, 1, 1, 1, 0, 6, 1, 1, 0, 6, 1, 1},
                {6, 1, 1, 1, 1, 0, 6, 1, 1, 0, 6, 1, 1}};
        return tempLayout;
    }

    /**
     * Getter for the list of clues going across
     *
     * @return the list of clues going across
     */
    public List<Clue> getCluesAcross() {
        return across;
    }

    /**
     * Getter for the list of clues going down
     *
     * @return the list of clues going down
     */
    public List<Clue> getCluesDown() {
        return down;
    }

    /**
     * Getter for crossword
     *
     * @return the crossword
     */
    public Crossword getCrossword() {
        return crossword;
    }

    /**
     * Getter for solution
     *
     * @return the solution to the crossword
     */
    public CrosswordSolution getSolution() {
        return solution;
    }

    /**
     * Getter for layout
     *
     * @return the layout of the board
     */
    public int[][] getLayout() {
        return layout;
    }

    /**
     * Generates the crossword
     *
     * @throws IOException              if there is an error in reading the CSV files
     * @throws CollabCrosswordException if there is an error in any of the steps
     * @throws CoordinatesException     if there an error related to any of the coordinates
     */
    public void generateCrossword() throws IOException,
            CollabCrosswordException, CoordinatesException {
        final FileReader reader = new FileReader();
        final Map<Integer, Clue> acrossMap = reader.getCluesMap(ACROSS);
        final Map<Integer, Clue> downMap = reader.getCluesMap(DOWN);

        across.addAll(acrossMap.values());
        down.addAll(downMap.values());

        layout = initBoardLayout();

        initCharMap();
        populateCharacterMapAcross(acrossMap);


        populateBoard();
        populateCurrentState();
        populateSolution();
    }

    /**
     * Populates the board
     *
     * @throws CollabCrosswordException if there is an error in any of the steps
     * @throws CoordinatesException     if there an error related to any of the coordinates
     */
    private void populateBoard() throws CollabCrosswordException,
            CoordinatesException {
        Tile[][] result = new Tile[dimX][dimY];
        for (int row = 0; row < dimX; row++) {
            for (int column = 0; column < dimY; column++) {
                if (charMap[row][column] == UNKNOWN) {
                    result[row][column] = TileFactory
                            .createBlockedTile(new Coordinates(row, column));
                } else {
                    result[row][column] = TileFactory
                            .createDefaultTile(new Coordinates(row, column));
                    if (layout[row][column] == START_OF_WORD) {
                        result[row][column].setLetterType(LetterType.START);
                        result[row][column].setShared(true);
                    } else if (layout[row][column] == START_ACROSS) {
                        result[row][column]
                                .setLetterType(LetterType.START_ACROSS);
                        result[row][column].setShared(true);
                    } else if (layout[row][column] == START_DOWN) {
                        result[row][column]
                                .setLetterType(LetterType.START_DOWN);
                        result[row][column].setShared(true);
                    } else {
                        result[row][column].setLetterType(LetterType.DEFAULT);
                        result[row][column].setShared(true);
                    }

                }
            }
        }

        crossword.setBoard(result);
    }

    /**
     * Populates the currentState
     *
     * @throws CollabCrosswordException if there is an error in any of the steps
     * @throws CoordinatesException     if there an error related to any of the coordinates
     */
    private void populateCurrentState() throws CollabCrosswordException, CoordinatesException {
        Tile[][] result = new Tile[dimX][dimY];
        for (int row = 0; row < dimX; row++) {
            for (int column = 0; column < dimY; column++) {
                if (charMap[row][column] == UNKNOWN) {
                    result[row][column] = TileFactory.createBlockedTile(new Coordinates(row, column));
                } else {
                    result[row][column] = TileFactory.createDefaultTile(new Coordinates(row, column));
                    if (layout[row][column] == START_OF_WORD) {
                        result[row][column].setLetterType(LetterType.START);
                        result[row][column].setShared(true);
                    } else if (layout[row][column] == START_ACROSS) {
                        result[row][column].setLetterType(LetterType.START_ACROSS);
                        result[row][column].setShared(true);
                    } else if (layout[row][column] == START_DOWN) {
                        result[row][column].setLetterType(LetterType.START_DOWN);
                        result[row][column].setShared(true);
                    } else {
                        result[row][column].setLetterType(LetterType.DEFAULT);
                        result[row][column].setShared(true);
                    }

                }
            }
        }
        currentState.setCurrentState(result);
    }

    /**
     * Populates the solution
     *
     * @throws CollabCrosswordException if there is an error in any of the steps
     * @throws CoordinatesException     if there an error related to any of the coordinates
     */
    private void populateSolution() throws CollabCrosswordException,
            CoordinatesException {
        Tile[][] result = new Tile[dimX][dimY];

        for (int row = 0; row < dimX; row++) {
            for (int column = 0; column < dimY; column++) {
                if (charMap[row][column] == UNKNOWN) {
                    result[row][column] = TileFactory
                            .createBlockedTile(new Coordinates(row, column));
                } else {
                    result[row][column] = TileFactory
                            .createDefaultTile(new Coordinates(row, column));
                    result[row][column].setLetter(charMap[row][column]);
                    if (layout[row][column] == START_OF_WORD) {
                        result[row][column].setLetterType(LetterType.START);
                        result[row][column].setShared(true);
                    } else if (layout[row][column] == START_ACROSS) {
                        result[row][column]
                                .setLetterType(LetterType.START_ACROSS);
                        result[row][column].setShared(true);
                    } else if (layout[row][column] == START_DOWN) {
                        result[row][column]
                                .setLetterType(LetterType.START_DOWN);
                        result[row][column].setShared(true);
                    } else {
                        result[row][column].setLetterType(LetterType.DEFAULT);
                        result[row][column].setShared(true);
                    }
                }
            }
        }
        solution.setSolution(result);
    }

    /**
     * Initializes the character map
     */
    private void initCharMap() {
        for (int row = 0; row < charMap.length; row++) {
            for (int column = 0; column < charMap[0].length; column++) {
                charMap[row][column] = UNKNOWN;
            }
        }
    }

    /**
     * Initializes the clue numbers
     */
    private void initClueNumbers() {
        for (int row = 0; row < clueNumberArray.length; row++) {
            for (int column = 0; column < clueNumberArray[0].length; column++) {
                clueNumberArray[row][column] = 0;
            }
        }
    }

    /**
     * Populates the character map
     *
     * @param clueMap the map of clues of words going across
     */
    private void populateCharacterMapAcross(final Map<Integer, Clue> clueMap) {
        int indexIntoClueMap = 0;
        int row = 0, column = 0;
        int wordIndex;
        char[] word = null;

        // call the two dimensional array initialization method
        initClueNumbers();
        try {

            for (final Clue clue : clueMap.values()) {
                word = clue.getAnswer().toCharArray();
                wordIndex = 0;
                while (wordIndex < word.length) {
                    if (canInsert(row, column, word.length - wordIndex)) {
                        charMap[row][column] = word[wordIndex];
                        if (layout[row][column] == START_OF_WORD
                                || layout[row][column] == START_ACROSS) {
                            clueNumberArray[row][column] = clue.getAbsoluteID();
                        }
                        wordIndex++;
                        column++;
                    } else {
                        if (column < dimX) {
                            column++;
                        } else {
                            column = 0;
                            row++;
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Getter for currentState
     *
     * @return the currentState
     */
    public CrosswordCurrentState getCrosswordCurrentState() {
        return currentState;
    }

    /**
     * Tells if a word can be inserted in the current row
     *
     * @param indexInRow the index from where the word has to be inserted
     * @param wordLength the length of the word
     * @return <tt>TRUE</tt> if the word can be inserted
     *         <tt>FALSE</tt> otherwise
     */
    private boolean canInsertInCurrentRow(final int indexInRow,
                                          final int wordLength) {
        return (indexInRow < dimY) && (indexInRow + wordLength <= dimY);
    }

    /**
     * Checks if a given number of characters can be inserted in a row
     *
     * @param row          the row where the characters need to be inserted
     * @param column       the column from where the insert has to start
     * @param numCharsLeft the number of characters
     * @return <tt>TRUE</tt> if it is possible to insert
     *         <tt>FALSE</tt> otherwise
     */
    private boolean canInsert(final int row, final int column,
                              final int numCharsLeft) {
        int charsThatCanBeInserted = 0;
        if (row < dimX && column < dimY) {
            for (int index = column; index < dimX; index++) {
                if (layout[row][index] == 1 || layout[row][index] == 3
                        || layout[row][index] == 6 || layout[row][index] == 7) {
                    charsThatCanBeInserted++;
                }
            }
            if (numCharsLeft > charsThatCanBeInserted) {
                return false;
            }

            if (row < dimX && column < dimY) {
                return layout[row][column] == DEFAULT
                        || layout[row][column] == START_OF_WORD
                        || layout[row][column] == START_ACROSS
                        || layout[row][column] == START_DOWN;
            }
        }
        return false;
    }
}
